// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// package ld -- go2cs converted at 2022 March 13 06:35:01 UTC
// import "cmd/link/internal/ld" ==> using ld = go.cmd.link.@internal.ld_package
// Original source: C:\Program Files\Go\src\cmd\link\internal\ld\macho_combine_dwarf.go
namespace go.cmd.link.@internal;

using bytes = bytes_package;
using zlib = compress.zlib_package;
using macho = debug.macho_package;
using binary = encoding.binary_package;
using fmt = fmt_package;
using io = io_package;
using os = os_package;
using reflect = reflect_package;
using @unsafe = @unsafe_package;

public static partial class ld_package {

private partial struct loadCmd {
    public macho.LoadCmd Cmd;
    public uint Len;
}

private partial struct dyldInfoCmd {
    public macho.LoadCmd Cmd;
    public uint Len;
    public uint RebaseOff;
    public uint RebaseLen;
    public uint BindOff;
    public uint BindLen;
    public uint WeakBindOff;
    public uint WeakBindLen;
    public uint LazyBindOff;
    public uint LazyBindLen;
    public uint ExportOff;
    public uint ExportLen;
}

private partial struct linkEditDataCmd {
    public macho.LoadCmd Cmd;
    public uint Len;
    public uint DataOff;
    public uint DataLen;
}

private partial struct encryptionInfoCmd {
    public macho.LoadCmd Cmd;
    public uint Len;
    public uint CryptOff;
    public uint CryptLen;
    public uint CryptId;
}

private partial struct loadCmdReader {
    public long offset;
    public long next;
    public ptr<os.File> f;
    public binary.ByteOrder order;
}

private static (loadCmd, error) Next(this ptr<loadCmdReader> _addr_r) {
    loadCmd _p0 = default;
    error _p0 = default!;
    ref loadCmdReader r = ref _addr_r.val;

    ref loadCmd cmd = ref heap(out ptr<loadCmd> _addr_cmd);

    r.offset = r.next;
    {
        var (_, err) = r.f.Seek(r.offset, 0);

        if (err != null) {
            return (cmd, error.As(err)!);
        }
    }
    {
        var err = binary.Read(r.f, r.order, _addr_cmd);

        if (err != null) {
            return (cmd, error.As(err)!);
        }
    }
    r.next = r.offset + int64(cmd.Len);
    return (cmd, error.As(null!)!);
}

private static error ReadAt(this loadCmdReader r, long offset, object data) {
    {
        var (_, err) = r.f.Seek(r.offset + offset, 0);

        if (err != null) {
            return error.As(err)!;
        }
    }
    return error.As(binary.Read(r.f, r.order, data))!;
}

private static error WriteAt(this loadCmdReader r, long offset, object data) {
    {
        var (_, err) = r.f.Seek(r.offset + offset, 0);

        if (err != null) {
            return error.As(err)!;
        }
    }
    return error.As(binary.Write(r.f, r.order, data))!;
}

// machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
//
// With internal linking, DWARF is embedded into the executable, this lets us do the
// same for external linking.
// exef is the file of the executable with no DWARF. It must have enough room in the macho
// header to add the DWARF sections. (Use ld's -headerpad option)
// exem is the macho representation of exef.
// dsym is the path to the macho file containing DWARF from dsymutil.
// outexe is the path where the combined executable should be saved.
private static error machoCombineDwarf(ptr<Link> _addr_ctxt, ptr<os.File> _addr_exef, ptr<macho.File> _addr_exem, @string dsym, @string outexe) => func((defer, panic, _) => {
    ref Link ctxt = ref _addr_ctxt.val;
    ref os.File exef = ref _addr_exef.val;
    ref macho.File exem = ref _addr_exem.val;

    var (dwarff, err) = os.Open(dsym);
    if (err != null) {
        return error.As(err)!;
    }
    defer(dwarff.Close());
    var (outf, err) = os.OpenFile(outexe, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0755);
    if (err != null) {
        return error.As(err)!;
    }
    defer(outf.Close());
    var (dwarfm, err) = macho.NewFile(dwarff);
    if (err != null) {
        return error.As(err)!;
    }
    defer(dwarfm.Close()); 

    // The string table needs to be the last thing in the file
    // for code signing to work. So we'll need to move the
    // linkedit section, but all the others can be copied directly.
    var linkseg = exem.Segment("__LINKEDIT");
    if (linkseg == null) {
        return error.As(fmt.Errorf("missing __LINKEDIT segment"))!;
    }
    {
        var err__prev1 = err;

        var (_, err) = exef.Seek(0, 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        (_, err) = io.CopyN(outf, exef, int64(linkseg.Offset));

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }

    var realdwarf = dwarfm.Segment("__DWARF");
    if (realdwarf == null) {
        return error.As(fmt.Errorf("missing __DWARF segment"))!;
    }
    var (compressedSects, compressedBytes, err) = machoCompressSections(_addr_ctxt, _addr_dwarfm);
    if (err != null) {
        return error.As(err)!;
    }
    var dwarfstart = Rnd(int64(linkseg.Offset), int64(FlagRound.val));
    {
        var err__prev1 = err;

        (_, err) = outf.Seek(dwarfstart, 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }

    {
        var err__prev1 = err;

        (_, err) = dwarff.Seek(int64(realdwarf.Offset), 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    } 

    // Write out the compressed sections, or the originals if we gave up
    // on compressing them.
    ulong dwarfsize = default;
    if (compressedBytes != null) {
        dwarfsize = uint64(len(compressedBytes));
        {
            var err__prev2 = err;

            (_, err) = outf.Write(compressedBytes);

            if (err != null) {
                return error.As(err)!;
            }

            err = err__prev2;

        }
    }
    else
 {
        {
            var err__prev2 = err;

            (_, err) = io.CopyN(outf, dwarff, int64(realdwarf.Filesz));

            if (err != null) {
                return error.As(err)!;
            }

            err = err__prev2;

        }
        dwarfsize = realdwarf.Filesz;
    }
    {
        var err__prev1 = err;

        (_, err) = exef.Seek(int64(linkseg.Offset), 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    var linkstart = Rnd(dwarfstart + int64(dwarfsize), int64(FlagRound.val));
    {
        var err__prev1 = err;

        (_, err) = outf.Seek(linkstart, 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        (_, err) = io.Copy(outf, exef);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    } 

    // Now we need to update the headers.
    var textsect = exem.Section("__text");
    if (textsect == null) {
        return error.As(fmt.Errorf("missing __text section"))!;
    }
    var cmdOffset = @unsafe.Sizeof(exem.FileHeader);
    {
        var is64bit = exem.Magic == macho.Magic64;

        if (is64bit) { 
            // mach_header_64 has one extra uint32.
            cmdOffset += @unsafe.Sizeof(exem.Magic);
        }
    }
    var dwarfCmdOffset = uint32(cmdOffset) + exem.FileHeader.Cmdsz;
    var availablePadding = textsect.Offset - dwarfCmdOffset;
    if (availablePadding < realdwarf.Len) {
        return error.As(fmt.Errorf("no room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding))!;
    }
    {
        var err__prev1 = err;

        (_, err) = outf.Seek(int64(dwarfCmdOffset), 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        (_, err) = io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len));

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        (_, err) = outf.Seek(int64(@unsafe.Offsetof(exem.FileHeader.Ncmd)), 0);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        var err = binary.Write(outf, exem.ByteOrder, exem.Ncmd + 1);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    {
        var err__prev1 = err;

        err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz + realdwarf.Len);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }

    ref loadCmdReader reader = ref heap(new loadCmdReader(next:int64(cmdOffset),f:outf,order:exem.ByteOrder), out ptr<loadCmdReader> _addr_reader);
    for (var i = uint32(0); i < exem.Ncmd; i++) {
        var (cmd, err) = reader.Next();
        if (err != null) {
            return error.As(err)!;
        }
        var linkoffset = uint64(linkstart) - linkseg.Offset;

        if (cmd.Cmd == macho.LoadCmdSegment64) 
            err = machoUpdateSegment(reader, _addr_linkseg, linkoffset);
        else if (cmd.Cmd == macho.LoadCmdSegment) 
            panic("unexpected 32-bit segment");
        else if (cmd.Cmd == LC_DYLD_INFO || cmd.Cmd == LC_DYLD_INFO_ONLY) 
            err = machoUpdateLoadCommand(reader, _addr_linkseg, linkoffset, addr(new dyldInfoCmd()), "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff");
        else if (cmd.Cmd == macho.LoadCmdSymtab) 
            err = machoUpdateLoadCommand(reader, _addr_linkseg, linkoffset, addr(new macho.SymtabCmd()), "Symoff", "Stroff");
        else if (cmd.Cmd == macho.LoadCmdDysymtab) 
            err = machoUpdateLoadCommand(reader, _addr_linkseg, linkoffset, addr(new macho.DysymtabCmd()), "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff");
        else if (cmd.Cmd == LC_CODE_SIGNATURE || cmd.Cmd == LC_SEGMENT_SPLIT_INFO || cmd.Cmd == LC_FUNCTION_STARTS || cmd.Cmd == LC_DATA_IN_CODE || cmd.Cmd == LC_DYLIB_CODE_SIGN_DRS || cmd.Cmd == LC_DYLD_EXPORTS_TRIE || cmd.Cmd == LC_DYLD_CHAINED_FIXUPS) 
            err = machoUpdateLoadCommand(reader, _addr_linkseg, linkoffset, addr(new linkEditDataCmd()), "DataOff");
        else if (cmd.Cmd == LC_ENCRYPTION_INFO || cmd.Cmd == LC_ENCRYPTION_INFO_64) 
            err = machoUpdateLoadCommand(reader, _addr_linkseg, linkoffset, addr(new encryptionInfoCmd()), "CryptOff");
        else if (cmd.Cmd == macho.LoadCmdDylib || cmd.Cmd == macho.LoadCmdThread || cmd.Cmd == macho.LoadCmdUnixThread || cmd.Cmd == LC_PREBOUND_DYLIB || cmd.Cmd == LC_UUID || cmd.Cmd == LC_VERSION_MIN_MACOSX || cmd.Cmd == LC_VERSION_MIN_IPHONEOS || cmd.Cmd == LC_SOURCE_VERSION || cmd.Cmd == LC_MAIN || cmd.Cmd == LC_LOAD_DYLINKER || cmd.Cmd == LC_LOAD_WEAK_DYLIB || cmd.Cmd == LC_REEXPORT_DYLIB || cmd.Cmd == LC_RPATH || cmd.Cmd == LC_ID_DYLIB || cmd.Cmd == LC_SYMSEG || cmd.Cmd == LC_LOADFVMLIB || cmd.Cmd == LC_IDFVMLIB || cmd.Cmd == LC_IDENT || cmd.Cmd == LC_FVMFILE || cmd.Cmd == LC_PREPAGE || cmd.Cmd == LC_ID_DYLINKER || cmd.Cmd == LC_ROUTINES || cmd.Cmd == LC_SUB_FRAMEWORK || cmd.Cmd == LC_SUB_UMBRELLA || cmd.Cmd == LC_SUB_CLIENT || cmd.Cmd == LC_SUB_LIBRARY || cmd.Cmd == LC_TWOLEVEL_HINTS || cmd.Cmd == LC_PREBIND_CKSUM || cmd.Cmd == LC_ROUTINES_64 || cmd.Cmd == LC_LAZY_LOAD_DYLIB || cmd.Cmd == LC_LOAD_UPWARD_DYLIB || cmd.Cmd == LC_DYLD_ENVIRONMENT || cmd.Cmd == LC_LINKER_OPTION || cmd.Cmd == LC_LINKER_OPTIMIZATION_HINT || cmd.Cmd == LC_VERSION_MIN_TVOS || cmd.Cmd == LC_VERSION_MIN_WATCHOS || cmd.Cmd == LC_VERSION_NOTE || cmd.Cmd == LC_BUILD_VERSION)         else 
            err = fmt.Errorf("unknown load command 0x%x (%s)", int(cmd.Cmd), cmd.Cmd);
                if (err != null) {
            return error.As(err)!;
        }
    } 
    // Do the final update of the DWARF segment's load command.
    return error.As(machoUpdateDwarfHeader(_addr_reader, compressedSects, dwarfsize, dwarfstart, _addr_realdwarf))!;
});

// machoCompressSections tries to compress the DWARF segments in dwarfm,
// returning the updated sections and segment contents, nils if the sections
// weren't compressed, or an error if there was a problem reading dwarfm.
private static (slice<ptr<macho.Section>>, slice<byte>, error) machoCompressSections(ptr<Link> _addr_ctxt, ptr<macho.File> _addr_dwarfm) {
    slice<ptr<macho.Section>> _p0 = default;
    slice<byte> _p0 = default;
    error _p0 = default!;
    ref Link ctxt = ref _addr_ctxt.val;
    ref macho.File dwarfm = ref _addr_dwarfm.val;

    if (!ctxt.compressDWARF) {
        return (null, null, error.As(null!)!);
    }
    var dwarfseg = dwarfm.Segment("__DWARF");
    slice<ptr<macho.Section>> sects = default;
    bytes.Buffer buf = default;

    foreach (var (_, sect) in dwarfm.Sections) {
        if (sect.Seg != "__DWARF") {
            continue;
        }
        if (sect.Nreloc != 0) {
            return (null, null, error.As(null!)!);
        }
        var (data, err) = sect.Data();
        if (err != null) {
            return (null, null, error.As(err)!);
        }
        var (compressed, contents, err) = machoCompressSection(data);
        if (err != null) {
            return (null, null, error.As(err)!);
        }
        ref var newSec = ref heap(sect.val, out ptr<var> _addr_newSec);
        newSec.Offset = uint32(dwarfseg.Offset) + uint32(buf.Len());
        newSec.Addr = dwarfseg.Addr + uint64(buf.Len());
        if (compressed) {
            newSec.Name = "__z" + sect.Name[(int)2..];
            newSec.Size = uint64(len(contents));
        }
        sects = append(sects, _addr_newSec);
        buf.Write(contents);
    }    return (sects, buf.Bytes(), error.As(null!)!);
}

// machoCompressSection compresses secBytes if it results in less data.
private static (bool, slice<byte>, error) machoCompressSection(slice<byte> sectBytes) {
    bool compressed = default;
    slice<byte> contents = default;
    error err = default!;

    ref bytes.Buffer buf = ref heap(out ptr<bytes.Buffer> _addr_buf);
    buf.WriteString("ZLIB");
    array<byte> sizeBytes = new array<byte>(8);
    binary.BigEndian.PutUint64(sizeBytes[..], uint64(len(sectBytes)));
    buf.Write(sizeBytes[..]);

    var z = zlib.NewWriter(_addr_buf);
    {
        var (_, err) = z.Write(sectBytes);

        if (err != null) {
            return (false, null, error.As(err)!);
        }
    }
    {
        var err = z.Close();

        if (err != null) {
            return (false, null, error.As(err)!);
        }
    }
    if (buf.Len() >= len(sectBytes)) {
        return (false, sectBytes, error.As(null!)!);
    }
    return (true, buf.Bytes(), error.As(null!)!);
}

// machoUpdateSegment updates the load command for a moved segment.
// Only the linkedit segment should move, and it should have 0 sections.
private static error machoUpdateSegment(loadCmdReader r, ptr<macho.Segment> _addr_linkseg, ulong linkoffset) {
    ref macho.Segment linkseg = ref _addr_linkseg.val;

    ref macho.Segment64 seg = ref heap(out ptr<macho.Segment64> _addr_seg);
    {
        var err__prev1 = err;

        var err = r.ReadAt(0, _addr_seg);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    } 

    // Only the linkedit segment moved, anything before that is fine.
    if (seg.Offset < linkseg.Offset) {
        return error.As(null!)!;
    }
    seg.Offset += linkoffset;
    {
        var err__prev1 = err;

        err = r.WriteAt(0, _addr_seg);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    } 
    // There shouldn't be any sections, but just to make sure...
    return error.As(machoUpdateSections(r, _addr_seg, linkoffset, null))!;
}

private static error machoUpdateSections(loadCmdReader r, ptr<macho.Segment64> _addr_seg, ulong deltaOffset, slice<ptr<macho.Section>> compressedSects) {
    ref macho.Segment64 seg = ref _addr_seg.val;

    var nsect = seg.Nsect;
    if (nsect == 0) {
        return error.As(null!)!;
    }
    var sectOffset = int64(@unsafe.Sizeof(seg));

    ref macho.Section64 sect = ref heap(out ptr<macho.Section64> _addr_sect);
    var sectSize = int64(@unsafe.Sizeof(sect));
    for (var i = uint32(0); i < nsect; i++) {
        {
            var err__prev1 = err;

            var err = r.ReadAt(sectOffset, _addr_sect);

            if (err != null) {
                return error.As(err)!;
            }

            err = err__prev1;

        }
        if (compressedSects != null) {
            var cSect = compressedSects[i];
            copy(sect.Name[..], cSect.Name);
            sect.Size = cSect.Size;
            if (cSect.Offset != 0) {
                sect.Offset = cSect.Offset + uint32(deltaOffset);
            }
            if (cSect.Addr != 0) {
                sect.Addr = cSect.Addr;
            }
        }
        else
 {
            if (sect.Offset != 0) {
                sect.Offset += uint32(deltaOffset);
            }
            if (sect.Reloff != 0) {
                sect.Reloff += uint32(deltaOffset);
            }
        }
        {
            var err__prev1 = err;

            err = r.WriteAt(sectOffset, _addr_sect);

            if (err != null) {
                return error.As(err)!;
            }

            err = err__prev1;

        }
        sectOffset += sectSize;
    }
    return error.As(null!)!;
}

// machoUpdateDwarfHeader updates the DWARF segment load command.
private static error machoUpdateDwarfHeader(ptr<loadCmdReader> _addr_r, slice<ptr<macho.Section>> compressedSects, ulong dwarfsize, long dwarfstart, ptr<macho.Segment> _addr_realdwarf) => func((_, panic, _) => {
    ref loadCmdReader r = ref _addr_r.val;
    ref macho.Segment realdwarf = ref _addr_realdwarf.val;

    var (cmd, err) = r.Next();
    if (err != null) {
        return error.As(err)!;
    }
    if (cmd.Cmd != macho.LoadCmdSegment64) {
        panic("not a Segment64");
    }
    ref macho.Segment64 seg = ref heap(out ptr<macho.Segment64> _addr_seg);
    {
        var err__prev1 = err;

        var err = r.ReadAt(0, _addr_seg);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    seg.Offset = uint64(dwarfstart);

    if (compressedSects != null) {
        ulong segSize = default;
        foreach (var (_, newSect) in compressedSects) {
            segSize += newSect.Size;
        }
    else
        seg.Filesz = segSize;
    } {
        seg.Filesz = dwarfsize;
    }
    seg.Addr = 0;
    seg.Memsz = 0;
    seg.Prot = 0;

    {
        var err__prev1 = err;

        err = r.WriteAt(0, _addr_seg);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    return error.As(machoUpdateSections(r, _addr_seg, uint64(dwarfstart) - realdwarf.Offset, compressedSects))!;
});

private static error machoUpdateLoadCommand(loadCmdReader r, ptr<macho.Segment> _addr_linkseg, ulong linkoffset, object cmd, params @string[] fields) {
    fields = fields.Clone();
    ref macho.Segment linkseg = ref _addr_linkseg.val;

    {
        var err__prev1 = err;

        var err = r.ReadAt(0, cmd);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    var value = reflect.Indirect(reflect.ValueOf(cmd));

    foreach (var (_, name) in fields) {
        var field = value.FieldByName(name);
        {
            var fieldval = field.Uint();

            if (fieldval >= linkseg.Offset) {
                field.SetUint(fieldval + linkoffset);
            }

        }
    }    {
        var err__prev1 = err;

        err = r.WriteAt(0, cmd);

        if (err != null) {
            return error.As(err)!;
        }
        err = err__prev1;

    }
    return error.As(null!)!;
}

} // end ld_package
